package cn.kinoko.common.aspect;

import cn.kinoko.common.aspect.annotation.Cacheable;
import cn.kinoko.common.utils.SpelUtil;
import com.github.benmanes.caffeine.cache.Cache;
import jakarta.annotation.Resource;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.concurrent.atomic.AtomicReference;

/**
 * 分布式缓存切面
 * @author kk
 */
@Getter
@Setter
@Aspect
@Slf4j
@Component
public class CacheableAspect {

    @Resource
    private Cache<String, Object> cache;

    @Around("@annotation(cacheable)")
    public Object around(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
        // 获取redisKey
        String redisKey = SpelUtil.getRedisKey(joinPoint, cacheable.key(), cacheable.params());
        // 取一个变量存储业务方法返回值
        AtomicReference<Object> result = new AtomicReference<>();
        // 获取缓存，无则执行业务方法构建缓存，此方法优化了并发场景下重复构建的问题
        Object cacheObj = cache.get(redisKey, key -> {
            try {
                // 执行业务方法
                Object target = joinPoint.proceed();
                // 检查条件，加入缓存
                if (target != null && !SpelUtil.getBoolean(joinPoint, cacheable.unless(), false)) {
                    // 设置缓存
                    return target;
                }
                result.set(target);
                return null;
            } catch (Throwable e) {
                // 继续抛出异常不处理
                throw new RuntimeException(e);
            }
        });
        if (cacheObj != null) log.info("[caffeine]cache hit {}", redisKey);
        return cacheObj == null ? result : cacheObj;
    }

}
